package com.uinnova.product.eam.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.binary.core.exception.BinaryException;
import com.binary.core.util.BinaryUtils;
import com.binary.framework.Local;
import com.binary.framework.bean.SimpleUser;
import com.binary.framework.exception.ServiceException;
import com.binary.jdbc.Page;
import com.google.common.collect.Lists;
import com.uinnova.product.eam.comm.model.es.EamArtifact;
import com.uinnova.product.eam.comm.model.es.EamDiagramRelationSys;
import com.uinnova.product.eam.constant.ArchReviewState;
import com.uinnova.product.eam.dao.ArchReviewInfoDao;
import com.uinnova.product.eam.dao.PlanProcessRelationDao;
import com.uinnova.product.eam.dao.SysApiKeyDao;
import com.uinnova.product.eam.dao.PlanHistoryDataDao;
import com.uinnova.product.eam.model.cj.domain.ChapterInstance;
import com.uinnova.product.eam.model.cj.domain.PlanChapterQuestion;
import com.uinnova.product.eam.model.cj.domain.PlanDesignInstance;
import com.uinnova.product.eam.model.cj.domain.PlanModuleAnnotationEntity;
import com.uinnova.product.eam.model.constants.NoticeConstant;
import com.uinnova.product.eam.model.es.*;
import com.uinnova.product.eam.service.EamDiagramRelationSysService;
import com.uinnova.product.eam.service.IEamArtifactSvc;
import com.uinnova.product.eam.service.InterfaceService;
import com.uinnova.product.eam.service.asset.BmConfigSvc;
import com.uinnova.product.eam.service.cj.dao.PlanChapterQuestionDao;
import com.uinnova.product.eam.service.cj.dao.PlanModuleAnnotationDao;
import com.uinnova.product.eam.service.cj.service.PlanChapterInstanceService;
import com.uinnova.product.eam.service.cj.service.PlanDesignInstanceService;
import com.uinnova.product.eam.web.asset.peer.ArchitectureAssetPeer;
import com.uinnova.product.vmdb.comm.model.ci.CCcCiClass;
import com.uinnova.product.vmdb.comm.model.ci.CcCi;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiClassInfo;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiInfo;
import com.uinnova.project.api.diagram.v2.client.ESDiagramApiClient;
import com.uinnova.project.base.diagram.comm.model.*;
import com.uinnova.project.db.eam.ESDiagramDao;
import com.uino.bean.cmdb.base.ESCIInfo;
import com.uino.bean.cmdb.base.LibType;
import com.uino.bean.cmdb.query.ESAttrBean;
import com.uino.bean.cmdb.query.ESCISearchBean;
import com.uino.bean.permission.base.SysUser;
import com.uino.dao.permission.ESUserSvc;
import com.uino.dao.util.ESUtil;
import com.uino.service.cmdb.microservice.ICIClassEncodeSvc;
import com.uino.service.cmdb.microservice.impl.CIClassSvc;
import com.uino.util.sys.SysUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Log4j2
public class InterfaceServiceImpl implements InterfaceService {

    @Resource
    private SysApiKeyDao sysApiKeyDao;
    @Resource
    private CIClassSvc ciClassSvc;
    @Resource
    private IamsCISwitchSvc iamsCISwitchSvc;
    @Resource
    private ESDiagramApiClient esDiagramApiClient;
    @Resource
    private EamDiagramRelationSysService eamDiagramRelationSysService;
    @Resource
    private ESDiagramDao esDiagramDao;
    @Resource
    private BmConfigSvc bmConfigSvc;
    @Resource
    private ArchitectureAssetPeer architectureAssetPeer;
    @Resource
    private ESUserSvc esUserSvc;
    @Resource
    private ICIClassEncodeSvc iciClassEncodeSvc;
    @Resource
    private IEamArtifactSvc iEamArtifactSvc;
    @Resource
    private ArchReviewInfoDao archReviewInfoDao;
    @Resource
    private PlanDesignInstanceService planDesignInstanceService;
    @Resource
    private PlanProcessRelationDao planProcessRelationDao;
    @Resource
    private PlanModuleAnnotationDao planModuleAnnotationDao;
    @Resource
    private PlanChapterQuestionDao planChapterQuestionDao;
    @Resource
    private PlanChapterInstanceService planChapterInstanceService;
    @Resource
    private PlanHistoryDataDao planHistoryDataDao;

    @Override
    public Page<ESCIInfo> assetList(AssetQueryInfo assetQueryInfo) {
        String sysName = assetQueryInfo.getSysName();
        String apiKey = assetQueryInfo.getApiKey();
        /**
         * 从视图配置中获取配置信息
         */
        String config = bmConfigSvc.getConfigType("INTERFACE_DATA_CONFIG");
        if (StrUtil.isEmpty(config)) {
            log.error("无法获取视图配置项：INTERFACE_DATA_CONFIG");
            throw new BinaryException("无法获取视图配置项：INTERFACE_DATA_CONFIG");
        }
        JSONObject jsonConfig = JSON.parseObject(config);

        String paasName = jsonConfig.getJSONObject("systemAuthMap").getString("PAAS");
        if (BinaryUtils.isEmpty(paasName)) {
            log.error("无法获取视图配置项：PAAS");
            throw new BinaryException("无法获取视图配置项：PAAS");
        }
        if (!sysName.equals(paasName)) {
            throw new BinaryException("该系统没有调用此接口权限！");
        }
        verifyPermission(sysName, apiKey);

        String className = jsonConfig.getJSONObject("classMap").getString(assetQueryInfo.getClassName());
        if (BinaryUtils.isEmpty(className)) {
            log.error("无法获取视图配置项：" + assetQueryInfo.getClassName());
            throw new BinaryException("无法获取视图配置项：" + assetQueryInfo.getClassName());
        }

        /**
         * 分类校验
         */
        CCcCiClass cdt = new CCcCiClass();
        cdt.setClassNameEqual(className);
        List<CcCiClassInfo> ciClassInfos = ciClassSvc.queryClassByCdt(cdt);
        if (CollectionUtils.isEmpty(ciClassInfos)) {
            log.info("未查询到资产分类[{}]", className);
            throw new BinaryException("未查询到资产分类！分类名称：" + className);
        }
        List<Long> ciClassIds = ciClassInfos.stream().map(ciiClassInfo ->
                ciiClassInfo.getCiClass().getId()).collect(Collectors.toList());
        ESCISearchBean bean = new ESCISearchBean();
        bean.setClassIds(ciClassIds);
        bean.setPageNum(assetQueryInfo.getPageNum());
        bean.setPageSize(assetQueryInfo.getPageSize());
        bean.setSortField("modifyTime");
        bean.setAsc(false);
        bean.setDomainId(1L);
        Page<ESCIInfo> page = iamsCISwitchSvc.searchESCIByBean(bean, LibType.DESIGN);
        return page;
    }

    /**
     * 根据系统名称获取系统apiKey列表
     * @param sysName
     * @return
     */
    private List<SysApiKeyInfo> getSysApiKeyBySysName(String sysName) {
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        query.must(QueryBuilders.termQuery("sysName.keyword", sysName));
        Page<SysApiKeyInfo> sysApiKeyInfoPage = sysApiKeyDao.getSortListByQuery(1, 3000, query, "createTime", true);
        return sysApiKeyInfoPage.getData();
    }

    @Override
    public JSONObject diagramJson(DiagramJsonQueryInfo diagramJsonQueryInfo) {
        String sysName = diagramJsonQueryInfo.getSysName();
        String apiKey = diagramJsonQueryInfo.getApiKey();

        /**
         * 从视图配置中获取配置信息
         */
        String config = bmConfigSvc.getConfigType("INTERFACE_DATA_CONFIG");
        if (StrUtil.isEmpty(config)) {
            log.error("无法获取视图配置项：INTERFACE_DATA_CONFIG");
            throw new BinaryException("无法获取视图配置项：INTERFACE_DATA_CONFIG");
        }
        JSONObject jsonConfig = JSON.parseObject(config);

        String paasName = jsonConfig.getJSONObject("systemAuthMap").getString("PAAS");
        if (BinaryUtils.isEmpty(paasName)) {
            log.error("无法获取视图配置项：PAAS");
            throw new BinaryException("无法获取视图配置项：PAAS");
        }
        if (!sysName.equals(paasName)) {
            throw new BinaryException("该系统没有调用此接口权限！");
        }
        verifyPermission(sysName, apiKey);

        String sysClassName = jsonConfig.getJSONObject("classMap").getString("系统");
        if (BinaryUtils.isEmpty(sysClassName)) {
            log.error("无法获取视图配置项：系统");
            throw new BinaryException("无法获取视图配置项：系统");
        }
        String viewType = jsonConfig.getString("productTypeId");
        if (BinaryUtils.isEmpty(viewType)) {
            log.error("无法获取视图配置项：productTypeId");
            throw new BinaryException("无法获取视图配置项：productTypeId");
        }
//        log.info(">>>>>>>>>> diagramJson >>>>>>>>>> sysClassName[{}]", sysClassName);
//        log.info(">>>>>>>>>> diagramJson >>>>>>>>>> viewType[{}]", viewType);

        /**
         * 查询系统分类信息
         */
        CCcCiClass cdt = new CCcCiClass();
        cdt.setClassNameEqual(sysClassName);
        List<CcCiClassInfo> ciClassInfos = ciClassSvc.queryClassByCdt(cdt);
        if (CollectionUtils.isEmpty(ciClassInfos)) {
            log.info("未查询到资产分类[{}]", sysClassName);
            throw new BinaryException("未查询到资产分类！分类名称：" + sysClassName);
        }

        /**
         * 查询资产信息
         */
        List<Long> ciClassIds = ciClassInfos.stream().map(ciiClassInfo ->
                ciiClassInfo.getCiClass().getId()).collect(Collectors.toList());
        ESCISearchBean bean = new ESCISearchBean();
        bean.setClassIds(ciClassIds);
        bean.setPageNum(1);
        bean.setPageSize(1);
        bean.setCiCodes(Collections.singletonList(diagramJsonQueryInfo.getCiCode()));
        bean.setDomainId(1L);
        Page<ESCIInfo> page = iamsCISwitchSvc.searchESCIByBean(bean, LibType.DESIGN);
        if (BinaryUtils.isEmpty(page) || BinaryUtils.isEmpty(page.getData())) {
            throw new BinaryException("未根据系统ciCode查询到相应的资产！ciCode：" + diagramJsonQueryInfo.getCiCode());
        }

        /**
         * 根据资产查询关联视图id
         */
        List<EamDiagramRelationSys> diagramRelations = eamDiagramRelationSysService.findDiagramRelationSysList(diagramJsonQueryInfo.getCiCode());
        if (BinaryUtils.isEmpty(diagramRelations)) {
            log.error("未根据系统ciCode查询到相应的发布视图！ciCode：" + diagramJsonQueryInfo.getCiCode());
            throw new BinaryException("未根据系统ciCode查询到相应的发布视图！ciCode：" + diagramJsonQueryInfo.getCiCode());

        }
        log.info(">>>>>>>>>>>>>>>>ciCode：" + diagramJsonQueryInfo.getCiCode());
        Set<String> diagramIds = diagramRelations.stream().map(EamDiagramRelationSys::getDiagramEnergy).collect(Collectors.toSet());
        log.info(">>>>>>>>>>>>>>>>diagramIds：" + diagramIds);

        /**
         * 根据视图id 查询已发布 相应制品类型下最新的视图
         */
        BoolQueryBuilder diagramQuery = QueryBuilders.boolQuery();
        diagramQuery.must(QueryBuilders.termQuery("dataStatus", 1));
        diagramQuery.must(QueryBuilders.termQuery("status", 1));
        diagramQuery.must(QueryBuilders.termQuery("historyVersionFlag", 1));
        // 视图类型 1:普通视图 3: 公共模板 4:个人模板
        diagramQuery.must(QueryBuilders.termsQuery("diagramType", Lists.newArrayList(1,2,3,4)));
        diagramQuery.must(QueryBuilders.termQuery("isOpen", 1));
        diagramQuery.must(QueryBuilders.existsQuery("ownerCode"));
        diagramQuery.must(QueryBuilders.termsQuery("dEnergy.keyword", diagramIds));
        diagramQuery.must(QueryBuilders.termQuery("viewType.keyword", viewType));

        List<SortBuilder<?>> sorts = new LinkedList<>();
        sorts.add(SortBuilders.fieldSort("modifyTime").order(SortOrder.DESC));
        Page<ESDiagram> diagramPage = esDiagramDao.getSortListByQuery(Integer.valueOf(1), Integer.valueOf(3000), diagramQuery, sorts);
        if (BinaryUtils.isEmpty(diagramPage) || BinaryUtils.isEmpty(diagramPage.getData())) {
            throw new BinaryException("未根据系统ciCode查询到相应的发布视图！ciCode：" + diagramJsonQueryInfo.getCiCode());
        }
        String diagramId = diagramPage.getData().get(0).getDEnergy();
        log.info(">>>>>>>>>>>>>>>>diagramId：" + diagramId);

        JSONObject jsonObject = new JSONObject();
        List<ESDiagramDTO> diagrams = esDiagramApiClient.queryFullDiagramByIds(Collections.singleton(diagramId));
        if (!BinaryUtils.isEmpty(diagrams)) {
            jsonObject = dealDiagramJson(diagrams.get(0));
        }
        List<EamArtifact> eamArtifacts = iEamArtifactSvc.queryArtifactListByIds(Collections.singletonList(Long.valueOf(viewType)), 1);
        jsonObject.put("diagramId", diagramId);
        jsonObject.put("diagramName", diagramPage.getData().get(0).getName());
        log.info(">>>>>>>>>>>>>>>>diagramName：" + diagramPage.getData().get(0).getName());
        log.info(">>>>>>>>>>>>>>>>userId：" + diagramPage.getData().get(0).getUserId());
        log.info(">>>>>>>>>>>>>>>>creator：" + diagramPage.getData().get(0).getCreator());
        jsonObject.put("createName", diagrams.get(0).getCreator().getUserName());
        log.info(">>>>>>>>>>>>>>>>createName：" + diagrams.get(0).getCreator().getUserName());
        jsonObject.put("createCode", diagrams.get(0).getCreator().getLoginCode());
        log.info(">>>>>>>>>>>>>>>>createCode：" + diagrams.get(0).getCreator().getLoginCode());
        jsonObject.put("assetName", getLabel(page.getData().get(0)));
        log.info(">>>>>>>>>>>>>>>>assetName：" + getLabel(page.getData().get(0)));
        jsonObject.put("productTypeName", eamArtifacts.get(0).getArtifactName());
        log.info(">>>>>>>>>>>>>>>>productTypeName：" + eamArtifacts.get(0).getArtifactName());
        jsonObject.put("createTime", diagramPage.getData().get(0).getCreateTime());
        log.info(">>>>>>>>>>>>>>>>createTime：" + diagramPage.getData().get(0).getCreateTime());
        jsonObject.put("modifyTime", diagramPage.getData().get(0).getModifyTime());
        log.info(">>>>>>>>>>>>>>>>modifyTime：" + diagramPage.getData().get(0).getModifyTime());
        return jsonObject;
    }

    private String getLabel(CcCi ci) {
        String ciLabel = ci.getCiLabel();
        if (StringUtils.isBlank(ciLabel)) {
            return null;
        }
        List<String> datas = JSONObject.parseArray(ciLabel, String.class);
        if (CollectionUtils.isEmpty(datas)) {
            return null;
        }
        return String.join("-", datas);
    }

    @Override
    public Long projectSave(ProjectInfo projectInfo) {
        /**
         * 从视图配置中获取配置信息
         */
        String config = bmConfigSvc.getConfigType("INTERFACE_DATA_CONFIG");
        if (StrUtil.isEmpty(config)) {
            log.error("无法获取视图配置项：INTERFACE_DATA_CONFIG");
            throw new BinaryException("无法获取视图配置项：INTERFACE_DATA_CONFIG");
        }
        JSONObject jsonConfig = JSON.parseObject(config);

        String paasName = jsonConfig.getJSONObject("systemAuthMap").getString("RDMP");
        if (BinaryUtils.isEmpty(paasName)) {
            log.error("无法获取视图配置项：RDMP");
            throw new BinaryException("无法获取视图配置项：RDMP");
        }
        if (!projectInfo.getSysName().equals(paasName)) {
            throw new BinaryException("该系统没有调用此接口权限！");
        }
        verifyPermission(projectInfo.getSysName(), projectInfo.getApiKey());

        if (!"项目".equals(projectInfo.getClassName())) {
            throw new BinaryException("只支持项目分类！");
        }
        String className = jsonConfig.getJSONObject("classMap").getString(projectInfo.getClassName());
        if (BinaryUtils.isEmpty(className)) {
            log.error("无法获取视图配置项：" + projectInfo.getClassName());
            throw new BinaryException("无法获取视图配置项：" + projectInfo.getClassName());
        }
        /**
         * 分类校验
         */
        CCcCiClass cdt = new CCcCiClass();
        cdt.setClassNameEqual(className);
        List<CcCiClassInfo> ciClassInfos = ciClassSvc.queryClassByCdt(cdt);
        if (CollectionUtils.isEmpty(ciClassInfos)) {
            log.info("未查询到资产分类[{}]", className);
            throw new BinaryException("未查询到资产分类！分类名称：" + className);
        }
        Long ciClassId = ciClassInfos.get(0).getCiClass().getId();

        /**
         * 用户校验
         */
        String ownerCode = projectInfo.getProjectOwner();
        BoolQueryBuilder must = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("loginCode.keyword", ownerCode));
        List<SysUser> sysUsers = esUserSvc.getListByQuery(must);
        if (BinaryUtils.isEmpty(sysUsers)) {
            throw new BinaryException("未查询到用户负责人！用户名：" + ownerCode);
        }
        SysUser sysUser = sysUsers.get(0);

        /**
         * 属性字段
         */
        JSONObject projectMap = jsonConfig.getJSONObject("projectMap");
        if (BinaryUtils.isEmpty(projectMap)) {
            log.error("无法获取视图配置项：projectMap");
            throw new BinaryException("无法获取视图配置项：projectMap");
        }
        BinaryUtils.checkEmpty(projectMap.getString("projectName"), "projectName");
        BinaryUtils.checkEmpty(projectMap.getString("preReviewNum"), "preReviewNum");
        BinaryUtils.checkEmpty(projectMap.getString("projectOwner"), "projectOwner");
        BinaryUtils.checkEmpty(projectMap.getString("requireDocUrl"), "requireDocUrl");
        BinaryUtils.checkEmpty(projectMap.getString("preReviewStatus"), "preReviewStatus");

        /**
         * 根据预审编号查询项目
         */
        ESCISearchBean bean = new ESCISearchBean();
        bean.setClassIds(Collections.singletonList(ciClassId));
        bean.setPageNum(1);
        bean.setPageSize(1);
        bean.setDomainId(1L);
        List<ESAttrBean> andAttrs = new ArrayList<>();
        ESAttrBean esAttrBean = new ESAttrBean();
        esAttrBean.setOptType(1);
        esAttrBean.setKey(projectMap.getString("preReviewNum"));
        esAttrBean.setValue(projectInfo.getPreReviewNum());
        andAttrs.add(esAttrBean);
        bean.setAndAttrs(andAttrs);
        Page<ESCIInfo> page = iamsCISwitchSvc.searchESCIByBean(bean, LibType.DESIGN);

        CcCiInfo ciInfo = new CcCiInfo();
        Map<String, String> attrs;

        /**
         * 如果项目存在则更新，如果不存在则新增
         */
        if (BinaryUtils.isEmpty(page) || BinaryUtils.isEmpty(page.getData())) {
            log.info("根据预审编号[{}]未查询到资产,按照新增处理", projectInfo.getPreReviewNum());
            CcCi ci = new CcCi();
            ci.setClassId(ciClassId);
            ci.setOwnerCode(ownerCode);
            ciInfo.setCi(ci);
            attrs = new HashMap<>();
            // 新增数据 生成内部编码
            JSONArray defaultValue = jsonConfig.getJSONArray("defaultValue");
            if (BinaryUtils.isEmpty(defaultValue)) {
                log.error("无法获取视图配置项：defaultValue");
                throw new BinaryException("无法获取视图配置项：defaultValue");
            }
            for (int i = 0; i < defaultValue.size(); i++) {
                JSONObject jsonObject = defaultValue.getJSONObject(i);
                Long maxNum = iciClassEncodeSvc.getMaxNum(jsonObject.getString("classCode"), jsonObject.getString("proName"), 1L, LibType.DESIGN);
                String value = jsonObject.getString("defVal") + "[" + maxNum + "]";
                attrs.put(jsonObject.getString("proName"), value);
            }

        } else {
            log.info("根据预审编号[{}]查询到资产,按照更新处理", projectInfo.getPreReviewNum());
            ESCIInfo esciInfo = page.getData().get(0);
            ciInfo.setCi(esciInfo);
            attrs = esciInfo.getAttrs().entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue() == null ? "" : String.valueOf(entry.getValue())));
        }
        attrs.put(projectMap.getString("projectName"),projectInfo.getProjectName());
        attrs.put(projectMap.getString("preReviewNum"),projectInfo.getPreReviewNum());
        List<Map<String, String>> owners = new ArrayList<>();
        Map<String, String> owner = new HashMap<>();
        owner.put("loginCode", ownerCode);
        owner.put("userName", sysUser.getUserName());
        owners.add(owner);
        attrs.put(projectMap.getString("projectOwner"),JSON.toJSONString(owners));
        attrs.put(projectMap.getString("requireDocUrl"),projectInfo.getRequireDocUrl() == null ? "" : projectInfo.getRequireDocUrl());
        attrs.put(projectMap.getString("preReviewStatus"),projectInfo.getPreReviewStatus() == null ? "" : projectInfo.getPreReviewStatus());
        ciInfo.setAttrs(attrs);

        // 校验是否存在架构资产的默认字段
        architectureAssetPeer.checkFillAssetDefAttr(ciInfo, LibType.DESIGN);
        Long id;
        try {
            boolean open = Local.isOpen();
            try {
                if(!open) {
                    SimpleUser user = new SimpleUser();
                    user.setId(sysUser.getId());
                    user.setDomainId(sysUser.getDomainId());
                    user.setLoginCode(sysUser.getLoginCode());
                    user.setUserName(sysUser.getUserName());
                    // 模拟当前登录用户
                    Local.open(user);
                }
                id = iamsCISwitchSvc.saveOrUpdateCI(ciInfo, LibType.DESIGN);
                if(!open) Local.commit();
            }catch(Throwable t) {
                Local.rollback();
                throw t;
            }finally {
                if(!open) Local.close();
            }
        }catch(Throwable t) {
            throw BinaryUtils.transException(t, ServiceException.class);
        }
        return id;
    }

    @Override
    public List<ProjectInfo> projectQuery(ProjectInfo projectInfo) {
        /**
         * 从视图配置中获取配置信息
         */
        String config = bmConfigSvc.getConfigType("INTERFACE_DATA_CONFIG");
        if (StrUtil.isEmpty(config)) {
            log.error("无法获取视图配置项：INTERFACE_DATA_CONFIG");
            throw new BinaryException("无法获取视图配置项：INTERFACE_DATA_CONFIG");
        }
        JSONObject jsonConfig = JSON.parseObject(config);

        String paasName = jsonConfig.getJSONObject("systemAuthMap").getString("RDMP");
        if (BinaryUtils.isEmpty(paasName)) {
            log.error("无法获取视图配置项：RDMP");
            throw new BinaryException("无法获取视图配置项：RDMP");
        }
        if (!projectInfo.getSysName().equals(paasName)) {
            throw new BinaryException("该系统没有调用此接口权限！");
        }
        verifyPermission(projectInfo.getSysName(), projectInfo.getApiKey());
        if (!"项目".equals(projectInfo.getClassName())) {
            throw new BinaryException("只支持项目分类！");
        }

        String className = jsonConfig.getJSONObject("classMap").getString(projectInfo.getClassName());
        if (BinaryUtils.isEmpty(className)) {
            log.error("无法获取视图配置项：" + projectInfo.getClassName());
            throw new BinaryException("无法获取视图配置项：" + projectInfo.getClassName());
        }
        /**
         * 分类校验
         */
        CCcCiClass cdt = new CCcCiClass();
        cdt.setClassNameEqual(className);
        List<CcCiClassInfo> ciClassInfos = ciClassSvc.queryClassByCdt(cdt);
        if (CollectionUtils.isEmpty(ciClassInfos)) {
            log.info("未查询到资产分类[{}]", className);
            throw new BinaryException("未查询到资产分类！分类名称：" + className);
        }
        Long ciClassId = ciClassInfos.get(0).getCiClass().getId();

        /**
         * 属性字段
         */
        JSONObject projectMap = jsonConfig.getJSONObject("projectMap");
        if (BinaryUtils.isEmpty(projectMap)) {
            log.error("无法获取视图配置项：projectMap");
            throw new BinaryException("无法获取视图配置项：projectMap");
        }
        BinaryUtils.checkEmpty(projectMap.getString("projectName"), "projectName");
        BinaryUtils.checkEmpty(projectMap.getString("preReviewNum"), "preReviewNum");
        BinaryUtils.checkEmpty(projectMap.getString("projectOwner"), "projectOwner");
        BinaryUtils.checkEmpty(projectMap.getString("requireDocUrl"), "requireDocUrl");
        BinaryUtils.checkEmpty(projectMap.getString("preReviewStatus"), "preReviewStatus");
        BinaryUtils.checkEmpty(projectMap.getString("archReviewStatus"), "archReviewStatus");
        BinaryUtils.checkEmpty(projectMap.getString("archPlanUrl"), "archPlanUrl");
        BinaryUtils.checkEmpty(projectMap.getString("reviewId"), "reviewId");
        BinaryUtils.checkEmpty(projectMap.getString("reviewStartTime"), "reviewStartTime");
        BinaryUtils.checkEmpty(projectMap.getString("reviewEndTime"), "reviewEndTime");

        /**
         * 查询资产信息
         */
        ESCISearchBean bean = new ESCISearchBean();
        bean.setClassIds(Collections.singletonList(ciClassId));
        bean.setPageNum(1);
        bean.setPageSize(3000);
        bean.setDomainId(1L);
        List<ESAttrBean> andAttrs = new ArrayList<>();
        if (!BinaryUtils.isEmpty(projectInfo.getPreReviewNum())) {
            ESAttrBean esAttrBean = new ESAttrBean();
            esAttrBean.setOptType(1);
            esAttrBean.setKey(projectMap.getString("preReviewNum"));
            esAttrBean.setValue(projectInfo.getPreReviewNum());
            andAttrs.add(esAttrBean);
        }
        if (!BinaryUtils.isEmpty(projectInfo.getProjectOwner())) {
            ESAttrBean esAttrBean = new ESAttrBean();
            esAttrBean.setOptType(2);
            esAttrBean.setKey(projectMap.getString("projectOwner"));
            esAttrBean.setValue(projectInfo.getProjectOwner());
            andAttrs.add(esAttrBean);
        }
        bean.setAndAttrs(andAttrs);
        Page<ESCIInfo> page = iamsCISwitchSvc.searchESCIByBean(bean, LibType.DESIGN);

        String currentDomainName = jsonConfig.getString("currentDomainName");
        if (BinaryUtils.isEmpty(currentDomainName)) {
            log.error("无法获取视图配置项：currentDomainName");
            throw new BinaryException("无法获取视图配置项：currentDomainName");
        }

        List<ProjectInfo> result = new ArrayList<>();
        if (!BinaryUtils.isEmpty(page) && !BinaryUtils.isEmpty(page.getData())) {
            for (ESCIInfo esciInfo : page.getData()) {
                Map<String, Object> ciAttrs = esciInfo.getAttrs();
                ProjectInfo project = new ProjectInfo();
                project.setProjectName(dealStrValue(ciAttrs.get(projectMap.getString("projectName"))));
                project.setPreReviewNum(dealStrValue(ciAttrs.get(projectMap.getString("preReviewNum"))));
                project.setProjectOwner(dealStrValue(ciAttrs.get(projectMap.getString("projectOwner"))));
                project.setRequireDocUrl(dealStrValue(ciAttrs.get(projectMap.getString("requireDocUrl"))));
                project.setPreReviewStatus(dealStrValue(ciAttrs.get(projectMap.getString("preReviewStatus"))));
                project.setArchReviewStatus(dealStrValue(ciAttrs.get(projectMap.getString("archReviewStatus"))));
                String archPlanUrl = String.valueOf(ciAttrs.get(projectMap.getString("archPlanUrl")));
                if (archPlanUrl != null && !"null".equals(archPlanUrl)) {
                    Map<String, String> archPlanUrlMap = new HashMap<>();
                    try {
                        Map<String, String> urlMap = JSON.parseObject(archPlanUrl, Map.class);
                        for (Map.Entry<String, String> entry : urlMap.entrySet()) {
                            archPlanUrlMap.put(entry.getKey(), currentDomainName + entry.getValue());
                        }
                    } catch (Exception exception) {
                        log.error("archPlanUrl转换失败！");
                        archPlanUrlMap.put("历史数据", archPlanUrl);
                    }
                    project.setArchPlanUrl(JSON.toJSONString(archPlanUrlMap));
                } else {
                    project.setArchPlanUrl("");
                }
                project.setReviewId(dealStrValue(ciAttrs.get(projectMap.getString("reviewId"))));
                project.setReviewStartTime(dealStrValue(ciAttrs.get(projectMap.getString("reviewStartTime"))));
                project.setReviewEndTime(dealStrValue(ciAttrs.get(projectMap.getString("reviewEndTime"))));
                result.add(project);
            }
        }

        return result;
    }

    /**
     * 属性值处理
     * @param attrValue
     * @return
     */
    private String dealStrValue(Object attrValue) {
        if (BinaryUtils.isEmpty(attrValue)) {
            return "";
        }
        String result = String.valueOf(attrValue);
        if (result == null || "null".equals(result)) {
            result = "";
        }
        return result;
    }

    /**
     * 处理视图json 和前端导出json逻辑保持一致
     * @param esDiagramDTO
     * @return
     */
    private JSONObject dealDiagramJson(ESDiagramDTO esDiagramDTO) {
        if (esDiagramDTO == null || esDiagramDTO.getDiagram() == null) {
            return null;
        }
        ESDiagramInfoDTO diagramInfoDTO = esDiagramDTO.getDiagram();

        // sheetList
        List<ESDiagramSheetDTO> sheetList = diagramInfoDTO.getSheetList();
        List<JSONObject> sheetJsonList = new ArrayList<>();
        for (ESDiagramSheetDTO sheet : sheetList) {
            JSONObject sheetJson = new JSONObject();
            sheetJson.put("id", sheet.getId());
            sheetJson.put("name", sheet.getName());
            sheetJson.put("sheetId", sheet.getSheetId());
            sheetJson.put("sheetOrder", sheet.getSheetId());
            sheetJson.put("active", false);
            sheetJsonList.add(sheetJson);
        }

        // modelList
        List<ESDiagramModel> modelList = diagramInfoDTO.getModelList();
        List<JSONObject> modelJsonList = new ArrayList<>();
        if (!BinaryUtils.isEmpty(modelList)) {
            for (ESDiagramModel model : modelList) {
                JSONObject modelJson = new JSONObject();
                modelJson.put("linkFromPortIdProperty", model.getLinkFromPortIdProperty());
                modelJson.put("linkToPortIdProperty", model.getLinkToPortIdProperty());
                modelJson.put("modelData", JSON.parse(model.getModelData()));
                modelJson.put("sheetId", model.getSheetId());
                modelJson.put("diagramId", model.getDiagramId());
                modelJson.put("class", "GraphLinksModel");
                modelJson.put("linkKeyProperty", "key");

                List<ESDiagramNode> nodeList = model.getNodeDataArray();
                List<JSONObject> nodeJsonList = new ArrayList<>();
                if (!BinaryUtils.isEmpty(nodeList)) {
                    for (ESDiagramNode node : nodeList) {
                        nodeJsonList.add(JSON.parseObject(node.getNodeJson()));
                    }
                }
                modelJson.put("nodeDataArray", nodeJsonList);

                List<ESDiagramLink> linkList = model.getLinkDataArray();
                List<JSONObject> linkJsonList = new ArrayList<>();
                if (!BinaryUtils.isEmpty(linkList)) {
                    for (ESDiagramLink link : linkList) {
                        linkJsonList.add(JSON.parseObject(link.getLinkJson()));
                    }
                }
                modelJson.put("linkDataArray", linkJsonList);

                modelJsonList.add(modelJson);
            }
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sheetList", sheetJsonList);
        jsonObject.put("modelList", modelJsonList);
        jsonObject.put("leftPanelModel", diagramInfoDTO.getLeftPanelModel());
        jsonObject.put("ident", "diagram_json");
        jsonObject.put("viewType", diagramInfoDTO.getViewType());
        jsonObject.put("colorHistoryMap", JSON.parseObject(diagramInfoDTO.getColorHistoryMap()));

        return jsonObject;
    }

    /**
     * 鉴权
     * @param sysName
     * @param apiKey
     */
    private void verifyPermission(String sysName, String apiKey) {
        List<SysApiKeyInfo> sysApiKeyInfos = getSysApiKeyBySysName(sysName);
        if (BinaryUtils.isEmpty(sysApiKeyInfos)) {
            throw new BinaryException("授权系统未查到！系统名称：" + sysName);
        }
        Set<String> apiKeys = sysApiKeyInfos.stream().map(SysApiKeyInfo::getApiKey).collect(Collectors.toSet());
        if (BinaryUtils.isEmpty(sysApiKeyInfos) || !apiKeys.contains(apiKey)) {
            throw new BinaryException("授权apiKey不匹配！系统名称：" + sysName + "apiKey：" + apiKey);
        }
    }

    @Override
    public void planHistoryData() {
        List<SortBuilder<?>> sorts = new ArrayList<>();
        FieldSortBuilder createTimeSort = SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
        sorts.add(createTimeSort);
        int pageNum = 1;
        int pageSize = 1000;
        /**
         * 只查询 审批完成同意的数据
         */
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        query.must(QueryBuilders.termQuery("state.keyword", ArchReviewState.PASS));
        Page<ArchReviewInfo> page = archReviewInfoDao.getSortListByQuery(pageNum, pageSize, query, sorts);
        if (CollectionUtils.isEmpty(page.getData())) {
            return;
        }
        /**
         * 1、已发布的方案 根据时间相近原则匹配对应的发布流程，生成流程与发布版本的关联关系
         * 2、已发布的方案根据对应的流程查询当时的批注和问题列表，生成对应版本的批注和问题数据
         * 3、记一下同步后新增的的数据id（关联的，批注的，问题的），如果有问题便于定位查询
         */
        for (ArchReviewInfo info : page.getData()) {
            //1.1 根据方案id查询对应的businessKey（此方案id是架构设计的方案id）
            List<PlanDesignInstance> plans = planDesignInstanceService.getByIds(info.getPlanIds());
            for (PlanDesignInstance plan : plans) {
//                log.info("架构设计方案id：【{}】,评审id：【{}】,审批流程id：【{}】,businessKey：【{}】", plan.getId(), info.getId(), info.getProcessInstanceId(), plan.getBusinessKey());
                //region 生成流程方案版本
                //1.2 根据businessKey查询已发布的方案列表中时间最为接近的一个方案
                //方案发布时，先根据当前时间生成一个新方案，再更新ArchReviewInfo的完成时间，所以按照ArchReviewInfo的完成时间往前查询创建时间最接近的一个方案
                BoolQueryBuilder planQuery = QueryBuilders.boolQuery();
                //设计资产方案
                planQuery.must(QueryBuilders.termQuery("assetsType", 2));
                planQuery.must(QueryBuilders.termQuery("businessKey.keyword", plan.getBusinessKey()));
                planQuery.must(QueryBuilders.rangeQuery("createTime").lte(info.getProcessFinishTime()));
                List<PlanDesignInstance> list = planDesignInstanceService.getSortListByQuery(1, 1, planQuery, sorts).getData();
                if (BinaryUtils.isEmpty(list)) {
                    log.info("方案id：【{}】,未查询到最近的发布方案！", plan.getId());
                    continue;
                }
                PlanDesignInstance publishedPlan = list.get(0);
//                log.info("设计资产方案id：【{}】", publishedPlan.getId());
                //查出时间最相近的一条数据，为避免匹配不准确，判断两个时间相差有没有超过3分钟，如果超过，表示有的方案被删掉了，则跳过
                boolean isOver = isOverThreeMinutes(info.getProcessFinishTime(), publishedPlan.getCreateTime());
                if (isOver) {
                    log.info("方案id：【{}】,查询到最近的发布方案与评审结束时间相差太大，匹配失败！", plan.getId());
                    continue;
                }
                // 查询是否已有关联数据
                BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("planId", publishedPlan.getId()));
                List<PlanProcessRelation> relations = planProcessRelationDao.selectListByQuery(1, 1, queryBuilder);
                if (!BinaryUtils.isEmpty(relations)) {
                    log.info("方案id：【{}】,方案名称：【{}】,在版本关联表中已有关联数据，跳过！", plan.getId(), plan.getName());
                    continue;
                }
                //1.3 根据查询出来的方案做流程与方案的关联关系保存
                Long relationId = ESUtil.getUUID();
                PlanProcessRelation planProcessRelation = new PlanProcessRelation();
                planProcessRelation.setId(relationId);
                planProcessRelation.setPlanId(publishedPlan.getId());
                planProcessRelation.setDesignPlanId(plan.getId());
                planProcessRelation.setProcessInstanceId(info.getProcessInstanceId());
                planProcessRelationDao.saveOrUpdate(planProcessRelation);
                //endregion

                //3.1 保存新增数据的关联关系id
                PlanHistoryData planHistoryData = new PlanHistoryData();
                planHistoryData.setPlanId(publishedPlan.getId());
                planHistoryData.setDesignPlanId(plan.getId());
                planHistoryData.setDataId(relationId);
                planHistoryData.setDataType("关联关系");
                planHistoryDataDao.saveOrUpdate(planHistoryData);

                //region 批注/问题版本保存
                //2.1 查询当前方案+流程的批注列表
                BoolQueryBuilder annotationQuery = QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("delFlag", false))
                        .must(QueryBuilders.termQuery("planId", plan.getId()))
                        .must(QueryBuilders.termQuery("taskDefinitionKey.keyword", info.getProcessInstanceId()));
                FieldSortBuilder sortBuilder = new FieldSortBuilder("createTime").order(SortOrder.DESC);
                List<PlanModuleAnnotationEntity> annotationEntities = planModuleAnnotationDao.getSortListByQuery(1, 3000, annotationQuery,
                        ListUtil.of(sortBuilder)).getData();
                //2.2 根据架构设计方案的的批注 生成对应的发布版本批注
                //2.2.1 已发布方案的章节id会生成新的id，批注绑定在章节id上，需要根据章节名称做一个匹配，查询两个方案的章节信息用于后续匹配
                //2.2.1.1 架构设计中的方案章节列表
                List<ChapterInstance> designChapterList = planChapterInstanceService.findChapterInstanceList(plan.getId(), null);
                //2.2.1.2 资产仓库中的方案章节列表
                List<ChapterInstance> publishedChapterList = planChapterInstanceService.findChapterInstanceList(publishedPlan.getId(), null);
                //2.2.1.3 设计方案根据章节id分组，资产方案根据章节名称分组
                Map<Long, String> designChapterMap = designChapterList.stream().collect(Collectors.toMap(ChapterInstance::getId, ChapterInstance::getName));
                Map<String, List<ChapterInstance>> publishedChapterMap = publishedChapterList.stream().collect(Collectors.groupingBy(item -> item.getName()));

                //2.2.2 映射批注id 批注原id与新批注id映射关系 用于保存问题时,问题的annotationId关联新生成的批注id
                Map<Long, Long> annotationIdMap = new HashMap<>();
                if (!BinaryUtils.isEmpty(annotationEntities)) {
                    //3.2 保存新增数据的批注id-组织数据
                    List<PlanHistoryData> annotationHistories = new ArrayList<>();
                    List<PlanModuleAnnotationEntity> annotationSaveList = new ArrayList<>();
                    //2.2.3 遍历批注列表 根据架构设计的章节id得到章节名称，再根据章节名称匹配资产方案中的章节id（如果根据章节名称匹配到多个，则舍弃）
                    for (PlanModuleAnnotationEntity annotationEntity : annotationEntities) {
                        Long oldAnnotationId = annotationEntity.getId();
                        Long annotationId = ESUtil.getUUID();
                        annotationEntity.setId(annotationId);
                        annotationEntity.setPlanId(publishedPlan.getId());
                        //根据章节id获取章节名称
                        String chapterName = designChapterMap.get(annotationEntity.getPlanChapterId());
                        //根据名称匹配资产方案中的章节信息
                        List<ChapterInstance> publishedChapters = publishedChapterMap.get(chapterName);
                        if (BinaryUtils.isEmpty(publishedChapters)) {
                            log.info("方案id：【{}】,章节id【{}】,章节名称【{}】,批注-没有匹配到资产方案中对应的章节信息！", plan.getId(), annotationEntity.getPlanChapterId(), chapterName);
                            continue;
                        }
                        if (publishedChapters.size() > 1) {
                            log.info("方案id：【{}】,章节id【{}】,章节名称【{}】,批注-根据章节名称查询到资产方案中多个章节信息，无法匹配！", plan.getId(), annotationEntity.getPlanChapterId(), chapterName);
                            continue;
                        }
                        annotationEntity.setPlanChapterId(publishedChapters.get(0).getId());
                        annotationIdMap.put(oldAnnotationId, annotationId);
                        annotationSaveList.add(annotationEntity);

                        //3.2.1 批注id数据组织
                        PlanHistoryData historyData = new PlanHistoryData();
                        historyData.setPlanId(publishedPlan.getId());
                        historyData.setDesignPlanId(plan.getId());
                        historyData.setDataId(annotationId);
                        historyData.setDataType("批注");
                        annotationHistories.add(historyData);
                    }
                    //2.2.4 保存该方案的批注列表
                    planModuleAnnotationDao.saveOrUpdateBatch(annotationSaveList);
                    //3.3 保存新增数据的批注id-保存数据
                    planHistoryDataDao.saveOrUpdateBatch(annotationHistories);
                }

                //2.3 查询当前方案+流程的问题列表
                BoolQueryBuilder questionQuery = QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("planId", plan.getId()))
                        .must(QueryBuilders.termQuery("processInstanceId.keyword", info.getProcessInstanceId()));
                List<PlanChapterQuestion> questions = planChapterQuestionDao.getSortListByQuery(1, 3000, questionQuery, "createTime", false).getData();
                if (!BinaryUtils.isEmpty(questions)) {
                    //3.4 保存新增数据的问题id-组织数据
                    List<PlanHistoryData> questionHistories = new ArrayList<>();
                    List<PlanChapterQuestion> questionSaveList = new ArrayList<>();
                    for (PlanChapterQuestion question : questions) {
                        Long questionId = ESUtil.getUUID();
                        question.setId(questionId);
                        question.setPlanId(publishedPlan.getId());
                        //根据章节id获取章节名称
                        String chapterName = designChapterMap.get(question.getPlanChapterId());
                        //根据名称匹配资产方案中的章节信息
                        List<ChapterInstance> publishedChapters = publishedChapterMap.get(chapterName);
                        if (BinaryUtils.isEmpty(publishedChapters)) {
                            log.info("方案id：【{}】,章节id【{}】,章节名称【{}】,问题-没有匹配到资产方案中对应的章节信息！", plan.getId(), question.getPlanChapterId(), chapterName);
                            continue;
                        }
                        if (publishedChapters.size() > 1) {
                            log.info("方案id：【{}】,章节id【{}】,章节名称【{}】,问题-根据章节名称查询到资产方案中多个章节信息，无法匹配！", plan.getId(), question.getPlanChapterId(), chapterName);
                            continue;
                        }
                        question.setPlanChapterId(publishedChapters.get(0).getId());
                        question.setAnnotationId(annotationIdMap.get(question.getAnnotationId()));
                        questionSaveList.add(question);

                        //3.4.1 问题id数据组织
                        PlanHistoryData historyData = new PlanHistoryData();
                        historyData.setPlanId(publishedPlan.getId());
                        historyData.setDesignPlanId(plan.getId());
                        historyData.setDataId(questionId);
                        historyData.setDataType("问题");
                        questionHistories.add(historyData);
                    }
                    //2.4 保存该方案的问题列表
                    planChapterQuestionDao.saveOrUpdateBatch(questionSaveList);
                    //3.5 保存新增数据的问题id-保存数据
                    planHistoryDataDao.saveOrUpdateBatch(questionHistories);
                }
                //endregion
            }
        }

        log.info("planHistoryData-历史数据同步完成");
    }

    /**
     * 判断两个时间相差是否超过3分钟
     * @param processFinishTime
     * @param createTime
     * @return
     */
    private boolean isOverThreeMinutes(Long processFinishTime, Long createTime) {
        // 将长整型时间转换为字符串
        String str1 = String.valueOf(processFinishTime);
        String str2 = String.valueOf(createTime);

        // 验证字符串长度是否为14位
        if (str1.length() != 14 || str2.length() != 14) {
            log.error("时间格式错误，必须为14位数字!");
            return true;
        }

        // 定义日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

        // 解析为LocalDateTime对象
        LocalDateTime dateTime1 = LocalDateTime.parse(str1, formatter);
        LocalDateTime dateTime2 = LocalDateTime.parse(str2, formatter);

        // 计算时间差的绝对值（秒）
        long secondsDiff = Math.abs(ChronoUnit.SECONDS.between(dateTime1, dateTime2));

        // 判断是否超过3分钟（180秒）
        return secondsDiff > 180;
    }

    @Override
    public void dealArchReviewInfo() {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("buildMode.keyword", "DEV_SELF"));
        archReviewInfoDao.updateByQuery(boolQuery, "ctx._source.buildMode='自主研发'", true);

        boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("buildMode.keyword", "COOPERATIVE"));
        archReviewInfoDao.updateByQuery(boolQuery, "ctx._source.buildMode='合作建设'", true);
    }

}
